#include "./mds.h"
#include <zlib.h>

int mds_file_save( const char *filename, const mds_frame *frame, Real t, int append)
{
	gzFile         *fp      = NULL;
	register size_t i       = 0;
	register size_t n       = 0;
	const char mode[4] = { (char)(append ? 'a' : 'w'), 'b', '9', 0 }; 
	assert( filename      );
	assert( frame != NULL );
	
	fp = gzopen( filename, mode );
	if( !fp )
		return -1;
	
	/** #particles **/
	gzprintf( fp, "n %u\n",  (n=frame->num_particles) );
	
	/** time     ***/
	gzprintf( fp, "t %.6e\n", t );
	
	/** box **/
	gzprintf( fp, "b %.6e %.6e %.6e\n", frame->box.x, frame->box.y, frame->box.z );
	
	/** enegetics **/
	gzprintf( fp, "v %.6e\n", frame->V);
	gzprintf( fp, "w %.6e\n", frame->W );
	
	/** particles **/
	for( i=0; i < n; ++i )
	{
		const mds_vector    *r  = & frame->sets[MDS_POSITION][i];
		const mds_vector    *v  = & frame->sets[MDS_VELOCITY][i];
		const mds_pnode     *p  = & frame->nodes[i];
		/** index mass inv_mass_flag r v ... */
		gzprintf( fp,
				 "%u %.4g %d %.6e %.6e %.6e %.6e %.6e %.6e\n",
				 (unsigned)(p->id), v->info.mass, r->info.inv_mass > 0 ? 1:0 , r->x, r->y, r->z, v->x, v->y, v->z);
	}
	
	gzclose( fp );
	return 0;
}

#include <errno.h>
#include <ctype.h>

#define LD_LINE_ERROR errno = EIO; return -1

static 
char *get_line( gzFile *fp, char *line, const size_t line_max )
{
	memset(line,0,line_max);
	if( gzgets(fp, line, line_max) )
	{
		size_t len = strlen(line);
		while( len > 0 && isspace(line[len-1]) ) line[--len] = 0;
		return line;
	}
	else {
		return NULL;
	}
	
}

static
int mds_file_read_next( mds_frame *frame, Real *t, gzFile *fp, char *line, const size_t line_max, unsigned *fidx )
{
	unsigned long np = 0;
	char          id = 0;
	double        tm = 0;
	double        bx=0,by=0,bz=0;
	unsigned      count = ++(*fidx);
	double        V = 0;
	double        W = 0;
	unsigned long i = 0;
	
	/** more data **/
	memset(line,0,line_max);
	if( ! get_line(fp,line,line_max) )
	{
		/** nope **/
		return 1;
	}
	
	/** get #particles **/
	if( sscanf( line, "%c %lu", &id, &np) != 2 || id != 'n' || np <= 0 )
	{
		fprintf( stderr, "** Frame #%u: Invalid FIRST line '%s'\n", count, line );
		LD_LINE_ERROR;
	}
	
	assert( np > 0 );
	/** create frame to get data **/
	if( np != frame->num_particles )
	{
		mds_frame_quit(frame);
		if( mds_frame_load( frame, np, 2) < 0 )
		{
			return -1;
		}
		np = frame->num_particles;
	}
	
	memset( frame->sets[MDS_POSITION], 0, np * sizeof(mds_vector) );
	memset( frame->sets[MDS_VELOCITY], 0, np * sizeof(mds_vector) );
	
	
	
	/** time **/
	if( !get_line(fp,line,line_max) || 2 != sscanf( line, "%c %lf", &id, &tm) || id != 't' )
	{
		fprintf( stderr, "** Frame #%u: Invalid TIME line '%s'\n", count, line );
		LD_LINE_ERROR;
	}
	*t = (Real)tm;
	
	/** box **/
	if( !get_line(fp,line,line_max) || 4 != sscanf( line, "%c %lf %lf %lf", &id, &bx, &by, &bz)  || id != 'b' )
	{
		fprintf( stderr, "** Frame #%u: Invalid BOX line '%s'\n", count, line );
		LD_LINE_ERROR;
	}
	frame->box.x = (Real)bx;
	frame->box.y = (Real)by;
	frame->box.z = (Real)bz;
	
	/** potential */
	if( !get_line(fp,line,line_max) || 2 != sscanf( line, "%c %lf", &id, &V) || id != 'v' )
	{
		fprintf( stderr, "** Frame #%u: Invalid POTENTIAL line '%s'\n", count, line );
		LD_LINE_ERROR;
	}
	frame->V = (Real)V;
	
	/** virial */
	if( !get_line(fp,line,line_max) || 2 != sscanf( line, "%c %lf", &id, &W) || id != 'w' )
	{
		fprintf( stderr, "** Frame #%u: Invalid VIRIAL line '%s'\n", count, line );
		LD_LINE_ERROR;
	}
	frame->W = (Real)W;
	
	/** particles **/
	assert(np==frame->num_particles);
	frame->K = 0;
	for( i=0; i < np; ++i )
	{
		unsigned long indx;
		double        mass;
		int           compute_imass;
		double rx=0, ry=0, rz=0;
		double vx=0, vy=0, vz=0;
		mds_vector *r=NULL;
		mds_vector *v=NULL;
		
		/** Read the Line **/
		if( !get_line(fp, line, line_max) )
		{
			fprintf( stderr, "** Frame #%u: Missing %u particle(s)\n", count, (unsigned)(np-i) );
			LD_LINE_ERROR;
		}
		
		/** scan the line **/
		if( 9 != sscanf( line, 
						"%lu %lf %d %lf %lf %lf %lf %lf %lf", 
						&indx, &mass, &compute_imass,
						&rx, &ry, &rz,
						&vx, &vy, &vz ) 
		   )
		{
			fprintf( stderr, "** Frame %u: Invalid particle '%s'", count, line );
			LD_LINE_ERROR;
		}
		
		/** fill particle **/
		r = & frame->sets[ MDS_POSITION ][indx];
		v = & frame->sets[ MDS_VELOCITY ][indx];
		
		if( v->info.mass > 0 ) 
		{
			fprintf( stderr,"** Frame %u: Multiple index in '%s'", count, line );
			LD_LINE_ERROR;
		}
		
		if( mass <= 0 )
		{
			fprintf( stderr, "** Frame %u: Invalid mass 'in %s'", count, line );
			LD_LINE_ERROR;
		}
		
		r->x = (Real)rx;
		r->y = (Real)ry;
		r->z = (Real)rz;
		r->info.inv_mass = compute_imass ? (Real)(1.0/mass) : 0;
		
		v->x = (Real)vx;
		v->y = (Real)vy;
		v->z = (Real)vz;
		v->info.mass = (Real)mass;
		
		frame->K += v->info.mass * mds_vector_normsq( v );
		
	}
	
	frame->K *= MDS_F(0.5);
	
	return 0;
}

#define LD_ERROR exit_code = -1; goto LOAD_END

int mds_file_load( const char *filename, mds_frame *frame, Real *t )
{
	gzFile         *fp        = NULL;
	char           *line      = NULL;
	const size_t    line_max  = 2048;
	int             exit_code = 0;
	unsigned        fidx      = 0; /*!< frame index */
	
	assert( filename );
	assert( t != NULL );
	
	/** open the file **/
	fp = gzopen( filename, "rb" );
	if( !fp )
		return -1;
	
	/** allocate line **/
	line = MDS_CALLOC_AS(char,line_max);
	if( !line ) 
	{
		LD_ERROR;
	}
	
	/** read frames **/
	{
		int res = 0;
		while( 0 == ( res = mds_file_read_next( frame, t, fp, line, line_max, &fidx) ) )
		{
			fprintf( stderr, "#"); fflush(stderr);
		}
		fprintf( stderr, "\n");
		if( res < 0 )
		{
			LD_ERROR;
		}
		if( 0 == frame->num_particles )
		{
			errno = EIO;
			fprintf( stderr, "** no particles in last frame!!!\n" );
			LD_ERROR;
		}
	}
	
	
	
LOAD_END:
	if( line ) MDS_FREE_AS(char,line,line_max);
	if( fp )   gzclose(fp);
	return exit_code;
}


int mds_file_load_and_apply( const char *filename, int (*proc)( const mds_frame *, Real t, void *args), void *args )
{
	
	gzFile         *fp        = NULL;
	char           *line      = NULL;
	const size_t    line_max  = 2048;
	int             exit_code = 0;
	unsigned        fidx      = 0; /*!< frame index */
	Real            t         = 0;
	mds_frame       frame     = MDS_FRAME_INIT;
	
	assert( filename );
	
	/** open the file **/
	fp = gzopen( filename, "rb" );
	if( !fp )
		return -1;
	
	/** allocate line **/
	line = MDS_CALLOC_AS(char,line_max);
	if( !line ) 
	{
		LD_ERROR;
	}
	
	/** read frames and call proc for each **/
	{
		int res = 0;
		while( 0 == ( res = mds_file_read_next( &frame, &t, fp, line, line_max, &fidx) ) )
		{
			if( proc( &frame, t, args ) < 0 )
			{
				fprintf( stderr, "\nprocessing failure!\n");
				LD_ERROR;
			}
			
		}
		if( res < 0 )
		{
			LD_ERROR;
		}
		
	}
	
	
	
LOAD_END:
	mds_frame_quit( &frame );
	if( line ) MDS_FREE_AS(char,line,line_max);
	if( fp )   gzclose(fp);
	return exit_code;
}
